﻿
    using System;
	using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
#if WinRT
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    using Windows.UI.Xaml.Markup;
    using Windows.UI.Xaml.Media;
#else
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Markup;
	using System.Windows.Media;
#endif

#if !SILVERLIGHT && !WinRT
    using System.Windows.Interop;
#endif


namespace DailyFx.UI
{
	using DailyFx.Common;


    /// <summary>
    ///		Hosts attached properties related to view models.
    /// </summary>
    public static class View
    {
        static readonly ILog Log = LogManager.GetLog( typeof(View) );

		/// <summary>
		///		the dummy context for ViewNav by default .
		/// </summary>
		public static readonly object DefaultContext = new object();

#if WinRT
        private const string DefaultContentProperty = "Content";
#else
		static readonly ContentPropertyAttribute DefaultContentProperty = new ContentPropertyAttribute("Content");
#endif

		/// <summary>
		///		dependency property for assigning a context to a particular portion of the UI.
		/// </summary>
		public static readonly DependencyProperty ContextProperty = DependencyProperty.RegisterAttached
			("Context", typeof(object), typeof(View), new PropertyMetadata(null, OnContextChanged));

		/// <summary>
		///		dependency property for attaching a model to the UI.
		/// </summary>
		public static DependencyProperty ModelProperty = DependencyProperty.RegisterAttached
			("Model", typeof(object), typeof(View), new PropertyMetadata(null, OnModelChanged));

		/// <summary>
		///		Used by the framework to indicate that this element was producted .
		/// </summary>
		public static readonly DependencyProperty IsCreatedProperty = DependencyProperty.RegisterAttached
			("IsCreated", typeof(bool), typeof(View), new PropertyMetadata(false));

		/// <summary>
		///		dependency property which allows the framework 
		///		to track whether a certain element has already been loaded in certain scenarios.
		/// </summary>
		public static readonly DependencyProperty IsLoadedProperty = DependencyProperty.RegisterAttached
			("IsLoaded", typeof(bool), typeof(View), new PropertyMetadata(false));

		/// <summary>
		///		dependency property which marks an element as a name scope root.
		/// </summary>
		public static readonly DependencyProperty IsScopeRootProperty = DependencyProperty.RegisterAttached
			("IsScopeRoot", typeof(bool), typeof(View), new PropertyMetadata(false));

		/// <summary>
		///		dependency property which allows the override of convention application behavior.
		/// </summary>
		public static readonly DependencyProperty ApplyConventionsProperty = DependencyProperty.RegisterAttached
			("ApplyConventions", typeof(bool?), typeof(View), null);


		/// <summary>
		/// 
		/// </summary>
		static void SetContentProperty ( object target, object view )
		{
			var frameworkElement = view as FrameworkElement;
			if (frameworkElement != null && frameworkElement.Parent != null)
			{
				SetContentPropertyImpl(frameworkElement.Parent, null);
			}

			SetContentPropertyImpl (target, view);
		}

#if WinRT
        static void SetContentPropertyImpl ( object target, object view )
        {
            try
            {
                var type = target.GetType();
                var contentPropertyName = GetContentProperty (type);

                type.GetRuntimeProperty(contentPropertyName)
                    .SetValue(targetLocation, view, null);
            }
            catch (Exception e)
            {
                Log.Error(e);
            }
        }

        private static string GetContentProperty ( Type type )
        {
            var typeInfo = type.GetTypeInfo();
            var contentProperty = typeInfo.CustomAttributes
                .FirstOrDefault( a => a.AttributeType == typeof( ContentPropertyAttribute) );

            return contentProperty == null
					? DefaultContentProperty 
					: contentProperty.NamedArguments[0].TypedValue.Value.ToString() ;
        }
#else
		static void SetContentPropertyImpl ( object target, object view )
		{
			try
			{
				var type = target.GetType();
				var contentProperty = type.GetAttributes<ContentPropertyAttribute>(true)
										  .FirstOrDefault() ?? DefaultContentProperty;

				type.GetProperty(contentProperty.Name)
					.SetValue(target, view, null);
			}
			catch (Exception e)
			{
				Log.Error(e);
			}
		}
#endif

		/// <summary>
		///		Sets the context.
		/// </summary>
		public static void SetContext ( DependencyObject d, object value )
		{
			d.SetValue(ContextProperty, value);
		}

		/// <summary>
		///		Gets the context.
		/// </summary>
		public static object GetContext ( DependencyObject d )
		{
			return d.GetValue(ContextProperty);
		}

		/// <summary>
		/// 
		/// </summary>
		static void OnContextChanged ( DependencyObject target, DependencyPropertyChangedEventArgs e )
        {
            if ( e.OldValue == e.NewValue)
            {
                return;
            }

            var model = GetModel( target );
            if (model == null)
            {
                return;
            }
			var view = View.CreateOrRetrieveViewElement(model, target, e.NewValue);

			SetContentProperty(target, view);
			Bind.SetBinding (model, view, e.NewValue);
        }

        /// <summary>
        ///		Sets the model.
        /// </summary>
        public static void SetModel ( DependencyObject d, object value )
        {
            d.SetValue(ModelProperty, value);
        }

        /// <summary>
        ///		Gets the model.
        /// </summary>
        public static object GetModel ( DependencyObject d )
        {
            return d.GetValue( ModelProperty );
        }

		/// <summary>
		/// 
		/// </summary>
		static void OnModelChanged ( DependencyObject target, DependencyPropertyChangedEventArgs args )
		{
			if (args.OldValue == args.NewValue)
			{
				return;
			}

			if ( args.NewValue != null)
			{
				var context = GetContext(target);
				var view = View.CreateOrRetrieveViewElement (args.NewValue, target, context);

				SetContentProperty(target, view);
				Bind.SetBinding (args.NewValue, view, context);
			}
			else
			{
				SetContentProperty ( target, args.NewValue);
			}
		}


		/// <summary>
        ///		Gets the convention application behavior.
        /// </summary>
        public static bool? GetApplyConventions ( DependencyObject d )
        {
            return (bool?)d.GetValue(ApplyConventionsProperty);
        }

        /// <summary>
        ///		Sets the convention application behavior.
        /// </summary>
        public static void SetApplyConventions ( DependencyObject d, bool? value )
        {
            d.SetValue ( ApplyConventionsProperty, value );
        }

		

        /// <summary>
        ///		Executes the handler immediately if the element is loaded, 
		///		 otherwise wires it to the Loaded event.
        /// </summary>
        public static bool CarryOnViewLoaded ( FrameworkElement element, RoutedEventHandler handler )
        {
#if SILVERLIGHT
            if ( (bool)element.GetValue(IsLoadedProperty) )
#elif WinRT
            if ( ((element.Parent ?? VisualTreeHelper.GetParent(element)) != null) || (Window.Current.Content != null && Window.Current.Content == element) )
#else
			if ( element.IsLoaded )
#endif
            {
                handler(element, new RoutedEventArgs() );
                return true;
            }
            else
            {
                RoutedEventHandler loaded = null;
                loaded = (s, e) =>
                {
#if SILVERLIGHT
                    element.SetValue(IsLoadedProperty, true);
#endif
                    handler(s, e);
                    element.Loaded -= loaded;
                };

                element.Loaded += loaded;
                return false;
            }
        }


        /// <summary>
        ///		Executes the handler the first time the elements's LayoutUpdated event fires.
        /// </summary>
#if WinRT
        public static void CarrayOnLayoutUpdated ( FrameworkElement element, EventHandler<object> handler)
		{
            EventHandler<object> onLayoutUpdated = null;
#else
		public static void CarrayOnLayoutUpdated (FrameworkElement element, EventHandler handler)
		{
            EventHandler onLayoutUpdated = null;
#endif
            onLayoutUpdated = (s, e) =>
			{
                handler(s, e);
                element.LayoutUpdated -= onLayoutUpdated;
            };

            element.LayoutUpdated += onLayoutUpdated;
        }


		/// <summary>
		/// 
		/// </summary>
		public static FrameworkElement CreateOrRetrieveViewElement ( object model, DependencyObject target, object context )
		{
			FrameworkElement view = null;

			IViewAware viewAware =  model as IViewAware ;
			if ( viewAware != null )
			{
				view = viewAware.GetView(context) as FrameworkElement ;
				if ( view != null )
				{
#if !SILVERLIGHT && !WinRT
					var window = view as System.Windows.Window;
					if (window == null || (!window.IsLoaded && !(new WindowInteropHelper(window).Handle == IntPtr.Zero)))
					{
						Log.Info("Using cached view for {0}.", model);
					}
#else
                    Log.Info("Using cached view for {0}.", model);
#endif
				}
			}

			if ( Designer.IfTrue )
			{
				// var viewTypeName = ModifyModelTypeAtDesigner ( modelType);
			}

			if ( view == null )
			{
				 // ViewModel resolves the view .
				 var attr = (ViewAttribute)model.GetType()
													.GetCustomAttributes( typeof(ViewAttribute), true)
													.FirstOrDefault();
				 if (attr == null)
				 {
					 Log.Warn("ViewNav attribute is missing for " + model.ToString());
					 return new TextBlock { Text = string.Format("Cannot find view for {0}.", model.GetType()) };
				 }
				 else
				 { 
					view = (FrameworkElement) Activator.CreateInstance( attr.ViewType );
					var method = view.GetType().GetMethod( "InitializeComponent", BindingFlags.Public | BindingFlags.Instance)
												.Invoke ( view, null );
				
					// todo: 
					/*
					view.Resources.MergedDictionaries.Add ( new ResourceDictionary
															{
																Source = new Uri(); // metro theme .
															});
			
					*/
				 }
		
			
			}
			
			// Simply binds data context to the view .
			// view.DataContext = context ;
			return view;
		}

		

        /// <summary>
        ///		Used to retrieve the root, non-framework-created view.
        /// </summary>
        /// <remarks>
		///		In certain cases, the services create UI elements on their own .
        ///		For example, if you ask the window manager to show a UserControl as a dialog , 
		///		it creates a window to host the UserControl in .
		///		
        ///		The WindowManager marks that element as a framework-created element so that 
		///		it can determine what it created vs. what was intended by the developer .
        ///		
		///		this method allows the framework to discover what the original element was . 
        /// </remarks>
        public static Func<object, object> GetFirstNonCreatedView = view =>
        {
            var dependencyObject = view as DependencyObject;
            if (dependencyObject == null)
            {
                return view;
            }

            if ((bool)dependencyObject.GetValue(IsCreatedProperty))
            {
                if (dependencyObject is ContentControl)
                {
                    return ((ContentControl)dependencyObject).Content;
                }
#if WinRT
                var type = dependencyObject.GetType();
                var contentPropertyName = GetContentPropertyName(type);

                return type.GetRuntimeProperty(contentPropertyName)
                    .GetValue(dependencyObject, null);
#else
                var type = dependencyObject.GetType();
                var contentProperty = type.GetAttributes<ContentPropertyAttribute>(true)
                                          .FirstOrDefault() ?? DefaultContentProperty;

                return type.GetProperty(contentProperty.Name)
                    .GetValue(dependencyObject, null);
#endif
            }

            return dependencyObject;
        };

		
		/// <summary>
		/// 
		/// </summary>
		public static bool ApplyViewSettings ( object target, IDictionary<string, object> settings )
		{
			if ( settings != null)
			{
				var type = target.GetType();

				foreach (var pair in settings)
				{
					var propertyInfo = type.GetProperty(pair.Key);
					if (propertyInfo != null)
					{
						propertyInfo.SetValue(target, pair.Value, null);
					}
				}
				return true;
			}
			return false;
		}

		/// <summary>
		///		Transforms a view type into a pack uri.
		/// </summary>
		public static string GetViewUri ( object view )
		{
#if !WinRT
			var viewType = view.GetType();
#else
			var viewType = view.GetType().GetTypeInfo();
#endif
			var viewAssembly = viewType.Assembly.FullName.Remove(viewType.Assembly.FullName.IndexOf(',') );

			var uri = viewType.FullName.Replace(viewAssembly, string.Empty).Replace(".", "/") + ".xaml";

			// check if is relative uri .
#if !WinRT
			var appType =  Application.Current.GetType();
#else
			var appType = Application.Current.GetType().GetTypeInfo();
#endif
			var appAssembly = appType.Assembly.FullName.Remove(appType.Assembly.FullName.IndexOf(','));

			if ( !appAssembly.Equals(viewAssembly) )
			{
				return "/" + viewAssembly + ";component" + uri;
			}

			return uri;
		}
    }



	public sealed class ViewAttribute : Attribute
	{
		/// <summary>
		///		the underlying type of ui element .
		/// </summary>
		private Type _viewType;


		/// <summary>
		/// 
		/// </summary>
		public ViewAttribute ( Type viewType )
		{
			this._viewType = viewType;
		}

		/// <summary>
		///		Gets the underlying type of ui element .
		/// </summary>
		public Type ViewType
		{
			get { return this._viewType; }
		}

	}
}